MSYS2 介绍与使用

MSYS2

MSYS2 的前世今生

Cygwin

这一切得从源头说起, 1995年Cygnus的一位工程师Steve Chamberlain, 他观察到Windows 系统使用COFF作为目标文件 (即可执行文件) 格式,与此同时GNU的工具链已经支持x86和COFF的目标文件,并提供C语言库newlib (Newlib 是嵌入式系统上的C标准库的实现) 。他认为既然GNU的工具链已经能够编译生成x86指令集的机器码并可链接生成COFF格式的目标文件, 而且还提供了可移植到任意平台的C标准库newlib, 那么理论上只要将GCC重定向(根据对应目标平台重新编译), 作为一个 cross compiler(交叉编译器),那么这个GCC 编译器不就可以生成 Windows 平台下的可执行文件了吗? 事实证明这是可行的, Steve Chamberlain 很快开发出了原型, 并把他这个项目命名为Cygwin.

现在 GCC 工具链有了,终于可以使用熟悉的GCC而不是微软的那一套MSVC来生成Windows 平台的应用程序了。不过,光有GCC 并不能很好的进行构建工作, 还需要 shell 脚本解释器以及那些与Uinx系统配套的一系列称手的工具也加入进来,因为只有这些工具的加持,GCC 才能用得舒服。因此 Steve Chamberlain 的下一步工作是尝试在 Windows 构建 shell 脚本运行环境, 因此需要一个与 Bourne shell 兼容的命令解释器(比如 bash), 而移植构建shell 解释器需要一个fork系统来实现程序的调用和标准输入/输出。Windows包含类似的功能, 但它们的接口和POSIX不兼容,这么一来Cygwin的库还需要提供与POSIX兼容的应用程序编程接口(API)。为了达到这个目的 Cygwin 提供了一套抽象层 dll,用于将部分 Posix 调用转换成 Windows 的 API 调用,实现相关功能。这里面最典型的,最基本的模拟层就是那个 cygwin1.dll。

这些工作早期都是由Steve Chamberlain一个人来完成, 但是到了1996年,由于看到cygwin可以提供Windows系统上的嵌入式工具链(以往的方案是使用DJGPP),其他工程师也加入了进来。特别吸引人的是,Cygwin可以实现three-way cross-compile(第三方交叉编译),也就是说可以在远程工作站上使用cygwin来编译其他平台的代码, 而操作工作站的电脑只是一台普通的pc, 该pc 本身不编译代码。由于工作站的CPU普遍更为强大,这比在PC上使用PC的速度更快。1998年起,Cygnus开始将Cygwin包作为产品来提供。

随着 Linux 系统的发展壮大,目前的 Cygwin 已经不仅仅提供 POSIX 兼容,因此也顺带多了更多模拟层的依赖关系。同时,由于它的模拟层实现了相当良好的 Posix 兼容,人们试着将许多重要的 Linux/BSD 应用移植到了Cygwin下,使得Cygwin越来越大,功能也越来越丰富,以至于目前很多人直接把将Linux应用移植到Windows平台的任务都交给了Cygwin(当然,这种移植并非原生)。

总结:从上面的描述可以看出, Cygwin 诞生之初,Steve Chamberlain 本来可能只是想通过GCC编译出Windows应用来,却因为 Posix 而顺带搞了一坨别的东西过来,最后发展到现在。可以说, Cygwin是运行于Windows平台的POSIX“子系统”,提供Windows下的类Unix环境,并提供将部分 Linux 应用“移植”到Windows平台的开发环境的一套软件。

MinGW

由于 Cygwin 的编译和调用方式总是需要依赖一层 POSIX 到 Windows API 的中间层, 比起日渐庞大的 Cygwin, 或许一个最小化且不需要中间层 GNU 工具链更能满足一些开发的需求, 于是 Colin Peters 在1998年创建了一个开源项目并撰写了最初的版本, 他将其命名为 mingw32 (Minimalist GNU for W32), 其意思就是Windows 上的最小化GNU 工具链, 这里的Windows被简称为 “W32”, 后来为了避免暗示它仅限于生成32位二进制文件, 后面的 32 被去除,从而变成了MinGW.

在最初的版本,Colin Peters首先为MSVCRT.DLL(Microsoft Visual C Runtime )库提供了一组头文件和一个导入库, 不过这些代码需要依赖和使用Cygwin的某些接口。之后的 Jan-Jaap van der Heijden希望不再依赖于Cygwin库,而是仅仅使用本机的构建环境来构建应用程序,所以Jan-Jaap着手开发了binutils、GCC和make,并生成了第一个无需使用Cygwin即可生成本机代码的编译器、汇编器和链接器工具。Mumit Khan 后来接手了开发工作,向程序包添加了更多Windows特定的功能,包括Anders Norlander的Windows系统标头。2000年,该项目移至SourceForge,以寻求社区的更多帮助并集中开发。 在 SourceForge 上得到了更多开源人士的帮助, MinGW也得到很大的发展,甚至2005年9月MinGW被选为SourceForge的月度最佳项目。不过在与SourceForge对其邮件列表的管理存在分歧之后,2018年,MinGW迁移到OSDN。

总结: MinGW 的出现是为了能够使用 GCC 工具链来构建不依赖于第三方运行库的应用程序, 相对于Cygwin,它首要特点就是简单性和性能好,因此,它不提供某些不能使用Windows API轻松实现的POSIX API,如fork()、mmap()和ioctl()。而用Cygwin编写的Windows程序总是需要运行在兼容层DLL之上,因此该DLL必须与程序的源代码一起分发。MinGW不需要兼容层,因为基于MinGW的程序是通过直接调用Windows API编译的。

MSYS

MinGW 出现之后, 为了更方便地使用 GNU 工具链, 需要提供一个可执行 shell 脚本的运行环境从而可以在shell 中调用MinGW 提供的那些工具如 gcc, make, linkr, 于是诞生了 MSYS(Minimal SYStem)。不过和 Cygwin 追求大而全的 POSIX 兼容系统不同, 其中间层 msys.dll 只提供基本的 POSIX API。因此MSYS可以看做是 Cygwin 的简化版, 但是因为一般 MSYS 都集成了MinGW, 所以当使用MSYS来构建应用程序时如果不使用一些特殊的 POSIX API时, 比如 fork, mmap 其实就是在使用 MinGW 编译代码, 其程序是通过直接调用Windows API编译的, 所以可以不需要依赖任何中间层和第三方DLL库, 也就是说, 这时 MSYS 在这个编译过程只是作为一个辅助工具箱来使用。

总结: MSYS 环境虽然也像 Cygwin 一样提供 POSIX API, 但是因为本着注重 Minimal(最小化)的原则, 其只提供了最基本的 API, 其兼容性相对于 Cygwin 较差, 但相对于庞大的 Cygwin, 结合 MinGW 的MSYS 可以编译不依赖于中间层DLL的应用程序,执行效率也更高,这个过程MSYS就相当于一个工具箱而已了。

MinGW-w64

可能是由于维护MinGW的人员工作太过于繁忙, 或是其他原因,MinGW 在后来更新速度异常缓慢,而且当初从ming32 改名为 MinGW, 去掉 32 是为了避免它暗示仅限于生成32位二进制文件, 这就意味着在创建项目的时候开发人员还考虑到将来会更新64位版本, 但是直到现在, MinGW 仍然只支持生成32位的Windows程序, 甚至开发人员以及不打算让其支持 64位的程序了。

2005年,因为最初的MinGW项目没有及时更新其代码库,包括包括几个关键的新API和急需的64位支持,OneVision Software 就创建了一个 MinGW 分支, 并把它命名为 MinGW-w64, 这里的 w64 是 windows 64 位的意思, 代表着支持生成 Windows 64位程序的MinGW, 但不限于64位, MinGW-w64 可以生成 32 位或 64 位的Windows 应用程序。

完成 64位的支持和API更新后 OneVision 将 MinGW-w64 代码提交给MinGW原始项目维护人员,但他们由于怀疑OneVision使用非公开或专有信息而将其拒绝。之后的 2008年,OneVision 将代码捐赠给了其主要开发人员之一的Kai Tietz, 其条件是该代码仍保持开源状态。由于许多原因,后来的MinGW-W64项目的主要开发人员和联合创始人Kai Tietz决定不再尝试与MinGW进一步合作了。

相对于 MinGW, MinGW-w64提供了更完整的Win32 API实现, 但是,MinGW-w64项目不提供官方的二进制构建,不过可以从开发人员的个人构建目录或者从相关但独立的项目(如tdm-gcc或mingw-builds或msys2)中获取。

总结: 因为 MinGW 更新缓慢以及不支持生成Windows 64位系统程序, 这才诞生了 MinGW-w64, 因此可以说MinGW-w64 就是 MinGW 的升级版, 是基于MinGW 的一个分支, 所以功能和MinGW一样,生成的程序是通过直接调用Windows API编译的而不需要中间层。

MSYS2

MSYS2(Minimal SYStem 2)是对MSYS的独立重写,和MinGW-w64 的诞生情况基本一致,因为 MSYS 太老了, 更新缓慢, 这才有了MSYS2,不过, 它是基于现代Cygwin和MinGW-w64,它的作用与以前的MSYS在MinGW中所起的作用相同。

作为项目,Cygwin和MSYS2有着明显不同的目标。

Cygwin试图为Windows带来一个与POSIX兼容的环境,这样在Unices上运行的大多数软件都可以在Cygwin上构建和运行,而无需进行任何重大修改。Cygwin提供了大量包含此类软件的软件包,以及用于开发这些软件的库。

MSYS2试图为构建本机Windows软件提供环境。MSYS2提供了包含此类软件的大量软件包,以及用于开发这些软件的库。由于该软件的很大一部分使用GNU构建工具,而GNU构建工具与Unix紧密耦合,因此该环境也与POSIX兼容,并且实际上是基于Cygwin的。

MSYS2提供了运行AutoTools和其他构建系统所需的最小外壳,这些构建系统从互联网上从不同的存储库获取软件源代码,并对其进行配置和构建。外壳和核心工具的存在主要是为了允许移植Unix程序在Windows上本地运行(即不需要POSIX模拟层)。MSYS2不会在必要时重复Cygwin的工作,因此提供的POSIX仿真软件的数量非常少。

MSYS2使用Pacman(出自Arch Linux)来管理其软件包,并附带三个不同的软件包存储库:
Msys2:包含依赖于MSYS2的软件。
Mingw64:包含64位原生Windows软件(使用mingw-W64 x86_64工具链编译)。
Mingw32:包含32位原生Windows软件(使用mingw-W64 i686工具链编译)。

总结: MSYS2 可看做是 MSYS 的升级版,它的作用与以前的MSYS在MinGW中所起的作用相同,并且基于MinGW-w64, 因此可以编译运行 32位和 64 位的程序。同时 MSYS2 使用 pacman 来管理软件包, 并且包含三个不同的软件包存储库,这意味着我们可以在不同的软件存储库之间切换。

MSYS2 使用

三个运行环境

安装自然不用说, 安装完成后生成三个快捷图标,

  • MSYS MinGW 32-bit
  • MSYS MinGW 64-bit
  • MSYS2 MSYS

对快捷图标进行查看属性, 我们可以看到其打开时执行的命令:

  • MSYS MinGW 32-bit 快捷图标的目标是:
    C:\Library\win64\msys64\msys2_shell.cmd -mingw32
    
  • MSYS MinGW 64-bit 快捷图标的目标是:
    C:\Library\win64\msys64\msys2_shell.cmd -mingw64
    
  • MSYS2 MSYS 快捷图标的目标是:
    C:\Library\win64\msys64\msys2_shell.cmd -msys
    

因此可以知道, 切换三个不同的环境是通过向脚本文件msys2_shell.cmd 中传递不同的参数实现的。于是我们在命令行中输入上面的任何一句话:

C:\Users\shino> C:\Library\win64\msys64\msys2_shell.cmd -mingw64
C:\Users\shino>

立刻弹出一个页面,进入 MSYS2 提供的默认终端, 环境显示的是 MINGW64 的

由此确定MSYS2 提供了一个msys2_shell.cmd 然后该批处理脚本可接受参数, 通过输入的参数来用于切换三个不同的环境类型。

那么如果不输入参数直接运行会怎么样:

C:\Users\shino> C:\Library\win64\msys64\msys2_shell.cmd
C:\Users\shino>

也弹出了窗口, 环境显示的是 MSYS

也就是说, 默认的环境是 MSYS 环境

msys2_shell.cmd 探索

既然 msys2_shell.cmd 可接受参数, 那么还可以接受那些参数呢? 一般来说命令程序都会提供一个 -h 或者 -help(–help)参数来提供给使用者提供帮助的, 于是尝试在cmd中输入如下内容:

C:\Users\shino> C:\Library\win64\msys64\msys2_shell.cmd -help
C:\Users\shino>

出现如下内容:

Usage:
   msys2_shell.cmd [options] [login shell parameters]

Options:
   -mingw32 | -mingw64 | -msys[2]   Set shell type
   -defterm | -mintty | -conemu     Set terminal type
   -here                            Use current >directory as working
                                    directory
   -where DIRECTORY                 Use specified >DIRECTORY as working
                                    directory
   -[use-]full-path                 Use full current PATH variable
                                    instead of trimming to minimal
   -no-start                        Do not use "start" command and
                                    return login shell resulting
                                    errorcode as this batch file
                                    resulting errorcode
   -shell SHELL                     Set login shell
   -help | --help | -? | /?         Display this help and exit

Any parameter that cannot be treated as valid option and all
following parameters are passed as login shell command parameters.

可以清楚地看到所有可接受的参数和其使用说明

  • 第一条是自然不必说, 就是设置我们的shell环境类型的.

  • 第二条是设置终端类型, 看这-defterm应该是default terminal(默认终端)的意思吧, 尝试一下:

    C:\Users\shino> C:\Library\win64\msys64\msys2_shell.cmd -defterm
    C:\Users\shino>
    

    仍旧弹出一个页面, 然而终端类型变成微软默认的终端了, 因为看左上角的图标, 已经不是 MSYS2 提供的终端了.

    用同样的方法, 在cmd 中分别调用C:\Library\win64\msys64\msys2_shell.cmd批处理并输入-mintty-conemu 参数观察到:

    输入 -mintty参数时, 弹出的窗口和不输入-mintty时是一致的, 由此得知 MSYS2 默认确实是使用 mintty 作为默认的终端, 输入-conemu 参数时, cmd 出现

    ConEmu not found. Exiting.
    

    这说明 MSYS2 并没有集成ConEmu终端,可能需要用户自己主动去下载才能使用.

  • 第三条和第四条参数显而易见, 当我们在终端打开 MSYS2 的某一个环境时, 通过 -here 参数可以使MSYS2 环境打开后立刻进入当前命令执行的环境, 如果没有 -here,参数, 则默认会进入 HOME 目录,而输入 -where + DIRECTORY 则可以进入指定文件夹.

  • -use-full-path(-full-path) 参数根据描述应该是将整个Windows 的PATH 添加到MSYS2 环境中的可选参数, 也就是说, 进入MSYS2 环境时,默认是把Windows 下的PATH环境给部分屏蔽掉的, 使用full-path将把PATH的所有内容添加进来, 这里可以验证一下, 我电脑安装了VSCode 并把其添加到了环境变量中去了, 这样一来只要在终端输入 code+[dir] 就可以打开VSCode 编辑器了。那么首先在不使用 -full-path 参数进入 MSYS2 环境:

    C:\Users\shino> C:\Library\win64\msys64\msys2_shell.cmd
    C:\Users\shino>
    

    进入 MSYS2 环境后输入 code

    shino@DESKTOP-2E1H5DE MSYS ~
    $ code
    bash: $'\302\203code': 未找到命令
    shino@DESKTOP-2E1H5DE MSYS ~
    $
    

    可以看到 MSYS2 并没能找到 code, 那么使用 -full-path 参数进入MSYS环境:

    C:\Users\shino> C:\Library\win64\msys64\msys2_shell.cmd -full-path
    C:\Users\shino>
    

    进入 MSYS2 环境后输入 code

    shino@DESKTOP-2E1H5DE MSYS ~
    $ code
    shino@DESKTOP-2E1H5DE MSYS ~
    $
    

    VSCode 编辑器已经打开, 并且程序正常执行并返回了。

    说明想要让MSYS2拥有完整的Windows PATH只需在使用msys2_shell.cmd进入环境时加入参数 -full-path即可,这也是官方默认且推荐的做法。除此之外, 还可以想到的方法是在 .bashrc 通过 export 手动来添加, 这种添加就比较灵活并且有更多的选择性。

    其实MSYS2 默认是有继承一部分 Windows 的环境变量的, 这个在我尝试使用 explorer 命令来打开某个文件夹时注意到了, 因为我在没有加入 -full-path 的情况下执行Windows 环境下的命令 explorer . 也是执行成功的。然后我通过 where 命令查看了 explorer 的位置:

    shino@DESKTOP-2E1H5DE MSYS ~
    $ where explorer
    C:\Windows\explorer.exe
    shino@DESKTOP-2E1H5DE MSYS ~
    $
    
  • -no-start 根据描述, 就是不需要再启动终端程序而是直接在当前终端进入 MSYS2 环境, 这一点非常不错, 因为很多时候我们只是想在当前终端下进入MSYS2 环境然后执行一些任务, 频繁地弹出弹框会让人厌烦。并且该参数使得 MSYS2 可以更好地和VSCode 编辑器或者其他编译器进行更好地集成和交互。打开cmd 测试一下:

    C:\Users\shino> C:\Library\win64\msys64\msys2_shell.cmd -defterm -no-start
    shino@DESKTOP-2E1H5DE MSYS ~
    $
    

    不再弹出弹框, 而是直接原地进入 MSYS2 环境

    需要注意的是: 在 cmd 里进入MSYS2 环境需要把终端类型设置成 -defterm 因为MSYS2 默认使用的是 mintty 终端, 因此没有 -defterm 它会先打开 mintty终端,然后执行 no-start 参数, 这不是我们想要的结果。

  • 最后一个参数 -shell 指定了进入环境时使用的 shell 解释器的类型, 默认是 bash。例如,当我们想使用当前比较流行的 zsh, 可以在启动时传入如下参数:

    C:\Users\shino> C:\Library\win64\msys64\msys2_shell.cmd -shell zsh
    C:\Users\shino>
    

    当然, 前提是你已经安装了zsh解释器, 在后面, 我们将讨论在 MSYS2 通过使用 pacman 包管理工具来进行方便的安装和卸载。

msys2_shell.cmd 使用

通过上面对 msys2_shell.cmd 的了解, 我们就可以更方便地使用 MSYS2 了, 下面举例几个比较典型的用法:

  1. 作为 VSCode 终端的环境: 我们想要实现在 VSCode 中打开终端时就默认进入了 MSYS2 的环境, 那么我们可以在setting.json 文件中添加如下内容:
      "terminal.integrated.shell.windows": "C:\\Library\\win64\\msys64\\msys2_shell.cmd",    
      "terminal.integrated.shellArgs.windows": ["-defterm", "-mingw64", "-no-start", "-here"],
    
  2. 按需进入 MSYS2 环境: 我们想要实现打开默认cmd的时候并不会立刻进入 MSYS2 环境, 而是在任何时刻我们想进入MSYS2 环境时只需输入 msys 就可以原地进入 MSYS2 环境, 这也是我比较推荐的做法。那么只需要新建一个 msys.bat 文件, 然后将其所在的目录放入环境变量 PATH 中即可:
    /c/Library/common $ ls
    msys.bat
    /c/Library/common $ cat msys.bat
    @C:\Library\win64\msys64\msys2_shell.cmd -defterm -mingw64 -no-start -here -shell zsh
    /c/Library/common $ 
    

我将 msys.bat 放到了 /c/Library/common 文件夹中, 内容如 cat 显示的那样, 只包含了一行命令就可以完成我们想要的工作, 将 /c/Library/common 文件夹加入到Windows 的PATH 环境变量中就可以在cmd中通过调用 msys 来切换MSYS2 的环境:

C:\Users\shino> msys
~ » 

在 cmd 中输入msys 后终端提示符变成了~ » ,这是因为我将默认 shell 设置成了 zsh 的结果

设置 HOME 目录

有时需要指定MSYS2 的home 目录的位置, 但是 msys2_shell.cmd 似乎没有这个选项, 目前只能想到的方案是修改 msys2_shell.cmd文件, 在文件较前面添加这样一行:

set "HOME=%USERPROFILE%"

= 后面是可选的目录, 这里将其设置得和 Windows 默认的一样

pacman 包管理

MSYS2 提供了非常方便的 pacman 包管理工具, 这个工具来自于 ArchLinux, 因此各种命令参数都是通用的。不过由于 MSYS2 提供了三个运行环境, 因此 pacman 管理的软件包也分三个来源,这三个源分别由一下三个文件管理:

/etc/pacman.d/mirrorlist.mingw32
/etc/pacman.d/mirrorlist.mingw64
/etc/pacman.d/mirrorlist.msys

国内使用 pacman 一般需要将软件源设置成国内的源,这样下载更新才会比较快,例如使用清华源:

编辑 /etc/pacman.d/mirrorlist.mingw32 ,在文件开头添加:
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/i686

编辑 /etc/pacman.d/mirrorlist.mingw64 ,在文件开头添加:
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/mingw/x86_64

编辑 /etc/pacman.d/mirrorlist.msys ,在文件开头添加:
Server = https://mirrors.tuna.tsinghua.edu.cn/msys2/msys/$arch

然后执行 pacman -Sy 刷新软件包数据即可。

因为 pacman 同时管理三个环境的软件源,因此在安装卸载等操作时需要提供完整的软件包名,如果不是完整的软件包名称,则pacman 默认操作的是 msys 环境下的软件, 例如 gcc, 三个环境的名称分别是:

MSYS: gcc
mingw64: mingw-w64-x86_64-gcc
mingw32: mingw-w64-i686-gcc

可以看到, 如果想要让 pacman 安装 mingw64环境下的gcc 则需要在gcc 前加入前置 mingw-w64-x86_64- , 其意义也十分的明显, mingw-w64就是我们上面所说的由OneVision 创建的MinGW-w64 环境, 它支持64位和32位, 因此x86_64则代表64位环境程序, i686则代表32 位的。

使用方法参考pacman

比如查询软件包, 使用

pacman -Sl | grep -E "mingw64.*opencv"

值得注意的是, 在 mingw64或mingw32 环境中, 如果对应环境的程序没有安装,则默认会去调用 MSYS 环境的程序, 例如 gcc 只安装了 MSYS 版本的, 在mingw64 环境中并没有安装gcc,但是输入 gcc 也可以执行, 因为这时候执行的是 MSYS 环境下的 gcc 程序,如果安装了自己环境下的gcc ,则调用的gcc 就变成当前环境下的gcc了。

举例: SDL2 安装和使用

使用 pacman 安装64位 SDL2 库并编译运行

  • 安装 SDL2

    pacman -S mingw-w64-x86_64-SDL2
    
  • demo.c

    #include <SDL2/SDL.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <stdio.h>
    
    int main(int argc, char *argv[])
    {
        SDL_Window *window = SDL_CreateWindow("demo", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_SHOWN);
        SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
        bool quit = false;
        SDL_Event event;
        
        printf("Hello World\n");
        
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
        while (!quit) {
            while (SDL_PollEvent(&event)) {
                switch (event.type) {
                case SDL_QUIT:
                    quit = true;
                    break;
                case SDL_MOUSEMOTION:
                    if(SDL_GetMouseState(NULL, NULL) &SDL_BUTTON(1))
                        SDL_RenderDrawPoint(renderer, event.motion.x, event.motion.y);
                    break;
                }
            }
    
            SDL_RenderPresent(renderer);
            SDL_Delay(1000 / 60);
        }
        SDL_DestroyWindow(window);
        SDL_DestroyRenderer(renderer);
        return 0;
    }
    
  • 编译运行

    ~/Desktop/SDL » gcc demo.c -lmingw32 -lSDL2main -lSDL2 -mwindows
    ~/Desktop/SDL » ./a.exe 
    ~/Desktop/SDL » 
    
  • 64
    点赞
  • 156
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值